package xyz.lwm.lazycat.utility.reflect;

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.lwm.lazycat.utility.StringUtil;
import xyz.lwm.lazycat.utility.cache.Cache;
import xyz.lwm.lazycat.utility.cache.WeakCaches;
import xyz.lwm.lazycat.utility.exception.ReflectionException;
import xyz.lwm.lazycat.utility.exception.UtilException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * reflect util
 *
 * @author lwm
 */
@Slf4j
public abstract class ReflectUtil {

    // 缓存 key 的后缀, 随机固定值, 防止缓存 key 重复
    private static final String KEY_SUFFIX = UUID.randomUUID().toString();

    /**
     * 创建对象
     *
     * @param type the type
     * @param <T>  the type
     * @return new instance
     */
    public static <T> T newInstance(@NotNull Class<T> type) {
        return newInstance(type, null);
    }

    /**
     * 创建对象
     *
     * @param type     the type
     * @param consumer consumer 如果存在, 则处理 type 的实例对象
     * @param <T>      the type
     * @return new instance
     */
    public static <T> T newInstance(@NotNull Class<T> type, @Nullable Consumer<T> consumer) {
        try {
            Constructor<T> constructor = type.getDeclaredConstructor();
            constructor.setAccessible(true);
            T t = constructor.newInstance();
            if (consumer != null) {
                consumer.accept(t);
            }
            return t;
        } catch (ReflectiveOperationException e) {
            throw new UtilException(e);
        }
    }

    /**
     * 获取字段值
     *
     * @param obj   the object
     * @param field field
     * @return value
     */
    public static Object getValue(@NotNull Object obj, @NotNull Field field) {
        try {
            field.setAccessible(true);
            return field.get(obj);
        } catch (ReflectiveOperationException e) {
            return null;
        }
    }

    /**
     * 设置字段值
     *
     * @param obj   the object
     * @param field field
     * @param val   value
     */
    public static void setValue(@NotNull Object obj, @NotNull Field field, @Nullable Object val) {
        try {
            field.setAccessible(true);
            field.set(obj, val);
        } catch (ReflectiveOperationException e) {
            //
        }
    }

    /**
     * 获取字段值
     *
     * @param obj      the object
     * @param property property name
     * @return value
     */
    public static Object getValue(@NotNull Object obj, @NotNull String property) {
        Map<String, Field> fieldMap = findDeclaredFields(obj.getClass());
        Field field = fieldMap.get(property);
        if (field != null) {
            return getValue(obj, field);
        }
        return null;
    }

    /**
     * 设置字段值
     *
     * @param obj      the object
     * @param property property name
     * @param val      value
     */
    public static void setValue(@NotNull Object obj, @NotNull String property, @Nullable Object val) {
        Map<String, Field> fieldMap = findDeclaredFields(obj.getClass());
        Field field = fieldMap.get(property);
        if (field != null) {
            setValue(obj, field, val);
        }
    }

    /**
     * 所有字段, 过滤静态字段及 Object.class 中的字段
     *
     * @param type the type
     * @return linked hash map of field name and field
     */
    public static Map<String, Field> findDeclaredFields(@NotNull Class<?> type) {
        return findDeclaredFields(type, null, f -> true, ReflectUtil.class.getName() +
                "#findDeclaredFields(" +  type.getName() + ")" + KEY_SUFFIX);
    }

    /**
     * 静态字段被过滤
     *
     * @param type        the type
     * @param typeFilter  type filter, 所有类型(包括父类)是否选择处理, (此遍历类的条件)
     * @param fieldFilter field filter, select field when use it
     * @param cacheKey    cache key
     * @return linked hash map of field name and field
     */
    public static Map<String, Field> findDeclaredFields(@NotNull Class<?> type,
                                                        @Nullable Predicate<Class<?>> typeFilter,
                                                        @Nullable Predicate<Field> fieldFilter,
                                                        @NotNull String cacheKey) {
        // object.class 不参与
        Predicate<Class<?>> baseTypeFilter = t -> t != null && t != Object.class;
        Predicate<Class<?>> finalTypeFilter = typeFilter != null ? typeFilter.and(baseTypeFilter) : baseTypeFilter;

        // 默认过滤掉静态字段
        Predicate<Field> baseFieldFilter = f -> !Modifier.isStatic(f.getModifiers());
        Predicate<Field> finalFieldFilter = fieldFilter != null ? fieldFilter.and(baseFieldFilter) : baseFieldFilter;

        Cache<Class<?>, Map<String, Field>> cache = WeakCaches.getCache(cacheKey);
        return cache.get(type, k -> {
            Map<String, Field> map = new LinkedHashMap<>();
            for (Class<?> clazz = Objects.requireNonNull(type); finalTypeFilter.test(clazz); clazz = clazz.getSuperclass()) {
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (finalFieldFilter.test(field)) {
                        map.put(field.getName(), field);
                    }
                }
            }
            return map;
        });
    }

    /**
     * getter 方法
     *
     * @param type the type
     * @return linked hash map of method name and method
     */
    public static Map<String, Method> findGetters(@NotNull Class<?> type) {
        return findDeclaredMethods(type, null, m -> {
            int modifiers = m.getModifiers();
            return !Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && m.getParameterCount() == 0 &&
                    m.getReturnType() != void.class && (m.getName().startsWith("get") || m.getName().startsWith("is"));
        }, m -> {
            if (m.getName().startsWith("get")) {
                return StringUtil.lowercaseChar(m.getName().substring(3), 0);
            } else {
                return StringUtil.lowercaseChar(m.getName().substring(2), 0);
            }
        }, ReflectUtil.class.getName() + "#findGetters(" + type.getName() + ")" + KEY_SUFFIX);
    }

    /**
     * getter 方法
     *
     * @param obj    the object
     * @param getter getter method
     * @return value
     */
    public static Object getValue(@NotNull Object obj, @NotNull Method getter) {
        try {
            return invoke(obj, getter);
        } catch (ReflectionException e) {
            return null;
        }
    }

    /**
     * setter 方法
     *
     * @param type the type
     * @return linked hash map of method name and method
     */
    public static Map<String, Method> findSetters(@NotNull Class<?> type) {
        return findDeclaredMethods(type, null, m -> {
                    int modifiers = m.getModifiers();
                    return !Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)
                            && m.getParameterCount() == 1 && m.getName().startsWith("set");
                }, m -> StringUtil.lowercaseChar(m.getName().substring(3), 0),
                ReflectUtil.class.getName() + "#findSetters(" + type.getName() + ")" + KEY_SUFFIX);
    }

    /**
     * set value
     *
     * @param obj    target
     * @param setter setter method
     * @param val    value
     */
    public static void setValue(@NotNull Object obj, @NotNull Method setter, @Nullable Object val) {
        try {
            invoke(obj, setter, val);
        } catch (ReflectionException e) {
            //
        }
    }

    /**
     * 静态方法被过滤
     *
     * @param type         the type
     * @param typeFilter   类型以及所有父类是否选择, 过滤 Object.class
     * @param methodFilter (先过滤掉静态方法后, 再添加 filter) 方法选择
     * @param nameMapper   方法名转 entry.key, 无则使用方法名
     * @param cacheKey     cache key
     * @return linked hash map of method name and method
     */
    public static Map<String, Method> findDeclaredMethods(@NotNull Class<?> type,
                                                          @Nullable Predicate<Class<?>> typeFilter,
                                                          @Nullable Predicate<Method> methodFilter,
                                                          @Nullable Function<Method, String> nameMapper,
                                                          @NotNull String cacheKey) {
        // object.class 不参与
        Predicate<Class<?>> baseTypeFilter = t -> t != null && t != Object.class;
        Predicate<Class<?>> finalTypeFilter = typeFilter != null ? typeFilter.and(baseTypeFilter) : baseTypeFilter;

        // 默认过滤掉静态方法
        Predicate<Method> baseMethodFilter = m -> !Modifier.isStatic(m.getModifiers());
        Predicate<Method> finalMethodFilter = methodFilter != null ? methodFilter.and(baseMethodFilter) : baseMethodFilter;

        Cache<Class<?>, Map<String, Method>> cache = WeakCaches.getCache(cacheKey);
        return cache.get(type, k -> {
            Map<String, Method> map = new LinkedHashMap<>();
            for (Class<?> clazz = Objects.requireNonNull(type); finalTypeFilter.test(clazz); clazz = clazz.getSuperclass()) {
                Method[] methods = clazz.getDeclaredMethods();
                for (Method m : methods) {
                    if (finalMethodFilter.test(m)) {
                        map.put(nameMapper != null ? nameMapper.apply(m) : m.getName(), m);
                    }
                }
            }
            return map;
        });
    }

    /**
     * 执行方法
     *
     * @param target 目标对象
     * @param method 被执行方法
     * @param args   执行参数
     */
    public static Object invoke(@NotNull Object target, @NotNull Method method, Object... args) {
        try {
            method.setAccessible(true);
            return method.invoke(target, args);
        } catch (Exception e) {
            throw new ReflectionException(e);
        }
    }

    /**
     * 判断是否是 equals 方法
     *
     * @param method method
     * @return true if equals method
     */
    public static boolean isEqualsMethod(Method method) {
        return method != null && method.getParameterCount() == 1 && "equals".equals(method.getName())
                && method.getReturnType() == boolean.class && method.getParameterTypes()[0] == Object.class;
    }

    /**
     * 判断是否是 hashCode 方法
     *
     * @param method method
     * @return true if hashCode method
     */
    public static boolean isHashCodeMethod(Method method) {
        return method != null && method.getParameterCount() == 0
                && "hashCode".equals(method.getName()) && method.getReturnType() == int.class;
    }

    /**
     * 判断是否是 toString 方法
     *
     * @param method method
     * @return true if toString method
     */
    public static boolean isToStringMethod(Method method) {
        return method != null && method.getParameterCount() == 0
                && "toString".equals(method.getName()) && method.getReturnType() == String.class;
    }

    /**
     * 判断是否是 Object 的方法 (equals, hashCode, toString)
     *
     * @param method method
     * @return true if Object method
     */
    public static boolean isObjectMethod(Method method) {
        return isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method);
    }

}
